/* * UIGen-Pro standard JavaScript library. * Copyright © 2004, 2005 Ubicom Inc. . All rights reserved. */ //*************************************************************************** // Utility functions /* * getAttribute() * Get the value of an attribute from a DOM node by name, or return * null if the attribute doesn't exist. Thanks to the overly complex and * generally sucky DOM standards, the proper way to do this is both * browser dependent and node-type dependent. Try a bunch of different * ways before giving up. */ function getAttribute (node,attribute_name) { /* * First try a fast way that works in newer versions of MSIE but not * in Netscape/Mozilla. */ if (node[attribute_name]) { return node[attribute_name]; } /* * Hmm, that didn't work, so try a (hopefully) more portable way that * should work with IE4+ and netscape. Of course it's quite possible * that the attribute doesn't exist, but there's no harm in trying * to access it a different way. */ if (node.getAttribute) { return node.getAttribute (attribute_name); } /* * Well that didn't work either. The node doesn't have the getAttribute() * function, so try another semi-portable XMLDOM-ish way. */ if (node.attributes) { var attr = node.attributes.getNamedItem (attribute_name); if (attr) return attr.nodeValue; } /* * Give up. */ return null; } /* * getEventObject() * This is called from event handling functions to find the object * that invoked this event. This is horribly browser dependent, * which is why we have a separate function to do it. * The parameter 'e' is what is passed to the event handling function. */ function getEventObject (evt) { evt = (evt) ? evt : ((window.event) ? window.event : ""); if (evt) { var obj = (evt.srcElement != null) ? evt.srcElement : evt.target; } else { var obj = null; } return obj; } /* * cloneObject() * Make a deep (i.e. recursive) cloned copy of the fields in the * given Object. Note that a=cloneObject(b) is different from a=b, * as the latter simply assigns a reference to the object to the * variable, making both a and b refer to the same instance, whereas * cloneObject() makes a separate instance. */ function cloneObject (obj) { var newobj = new Object for (var i in obj) { if (typeof(obj[i]) == "object") { newobj[i] = cloneObject (obj[i]); } else { newobj[i] = obj[i]; } } return newobj; } /* * copyObject() * Make a deep (i.e. recursive) cloned copy of the fields in the * source Object, copying them to the destination object. * The destination is presumed to already have the necessary * object members. */ function copyObject (dest,src,field_name_to_ignore) { for (var i in src) { if (typeof(src[i]) == "object") { copyObject (dest[i],src[i]); } else { if (i != field_name_to_ignore) { dest[i] = src[i]; } } } } /* * compareObject() * Make a deep (i.e. recursive) comparison of the fields in the * A and B objects. return 1 if the same or 0 if not. * B object is presumed to have the same object members as A, * however if B has any members not present in A they will be * ignored. */ function compareObject (A,B,field_name_to_ignore) { for (var i in A) { if (typeof(B[i]) == "undefined") continue; if (typeof(A[i]) == "object") { if (compareObject (A[i],B[i])==0) return 0; } else { if (i != field_name_to_ignore && A[i] != B[i]) { return 0; } } } return 1; } /* * appendArray() * Append array B to array A, but don't use concat() because that * would create a separate array object, resulting in poor performance. */ function appendArray (A,B) { var k = A.length; var n = B.length; for (var i=0; i> 18) & 0x3f) + b64.charAt((x >> 12) & 0x3f) + b64.charAt((x >> 6) & 0x3f) + b64.charAt(x & 0x3f); } return barray.join (""); } /* * buildCharToSixBitsArray() * Build a 256 entry array that convert a base-64 character * to its corresponding 6-bit number. Invalid characters will * be translated to 0. */ function buildCharToSixBitsArray() { b64_char_to_6 = new Array; for (var i=0; i<256; i++) b64_char_to_6[i] = 0; for (var i=0; i<26; i++) b64_char_to_6[i+65] = i+1; for (var i=0; i<26; i++) b64_char_to_6[i+97] = i+27; for (var i=0; i<10; i++) b64_char_to_6[i+48] = i+53; b64_char_to_6[95] = 63; } buildCharToSixBitsArray(); /* * convertFromBase64() * Read base-64 encoded binary data from the string `buf', and return * the resulting array of 8-bit numbers. Invalid characters in the * string will be interpreted as '.'. */ function convertFromBase64 (buf) { while (buf.length & 3) buf += '.'; // make 'buf' a multiple of 4 var dst = new Array; var j = 0; for (var i = 0; i < buf.length; i += 4) { var cc = (b64_char_to_6 [buf.charCodeAt(i)] << 18) | (b64_char_to_6 [buf.charCodeAt(i+1)] << 12) | (b64_char_to_6 [buf.charCodeAt(i+2)] << 6) | (b64_char_to_6 [buf.charCodeAt(i+3)]); dst[j] = (cc >> 16) & 0xff; dst[j+1] = (cc >> 8) & 0xff; dst[j+2] = (cc) & 0xff; j += 3; } return dst; } //*************************************************************************** // Conversion functions: convert data between the string representation used // by form elements and the binary array representation used to pass data to // and from the server. /* * intToByteArray() * Convert an integer value to a byte array of size 'length'. */ function intToByteArray (value,length) { var a = new Array; for (var i=0; i>> ((length-1-i)*8)) & 0xff; } return a; } /* * byteArrayToInt() * Convert the byte array indexes start_index to end_index-1 to an integer. * The number of bytes in the range must be is 1 to 4. * If 4 bytes are converted then the returned value will be signed, * otherwise the returned value will be unsigned for 1-3 bytes converted. */ function byteArrayToInt (b,start_index,end_index) { var n=0; for (var i=start_index; i= length) break; a[j] = c; j += 1; } else if (c <= 0x7FF) { if (j+1 >= length) break; a[j] = (c >> 6) | 0xc0; a[j+1] = (c & 0x3f) | 0x80; j += 2; } else if (c <= 0xFFFF) { if (j+2 >= length) break; a[j] = (c >> 12) | 0xe0; a[j+1] = ((c >> 6) & 0x3f) | 0x80; a[j+2] = (c & 0x3f) | 0x80; j += 3; } else if (c <= 0x10FFFF) { if (j+3 >= length) break; a[j+0] = (c >> 24) | 0xf0; a[j+1] = ((c >> 12) & 0x3f) | 0x80; a[j+2] = ((c >> 6) & 0x3f) | 0x80; a[j+3] = (c & 0x3f) | 0x80; j += 4; } else { // Default behavior when we don't know what else to do if (j >= length) break; a[j] = c; j += 1; } } a[length] = 0; return a; } /* * byteArrayToString() * Convert a byte array to a string. Only array indexes * between start_index and end_index-1 are examined. * UTF-8 byte sequences are converted properly. */ function byteArrayToString (b,start_index,end_index) { var s = ""; for (var i=start_index; i 255) return null; x = x | (q << ((4-i)*8)); } return x; } else return null; } /* * IPAddressToByteArray() * Convert an IP address formatted string into a 4 element byte array. * Return 0 if the string is not formatted correctly. */ function IPAddressToByteArray (s) { s = new String(s); var got = s.match (/^\s*(\d+)\s*[.]\s*(\d+)\s*[.]\s*(\d+)\s*[.]\s*(\d+)\s*$/); if (got) { var a = new Array; for (var i=1; i <= 4; i++) { var q = parseInt(got[i],10); if (q < 0 || q > 255) return 0; a[i-1] = q; } return a; } else return 0; } /* * byteArrayToIPAddress() * Convert a byte array 'a' into an IP address formatted string. * Elements in 'a' from start_index to start_index+3 are used. */ function byteArrayToIPAddress (a,start_index) { var s = ""; for (var i=0; i < 4; i++) { s += a[i+start_index].toString(10); if (i < 3) s += "."; } return s; } /* * hexStringToByteArray() * Convert a hex string 's' into a byte array of 'length' bytes. * If 's' is shorter than 'length' bytes, the result will be padded with zeros. * Ignore leading and trailing whitespace. If there are any conversion errors * or the string is invalid then return 0, else return a byte array. * If the string is empty or all whitespace, return an array of all zeros. * Ignore the commonly used separator characters (:,-) */ function hexStringToByteArray (s,length) { // Ignore the commonly used separator characters s = new String(s); s = s.replace (/[:-]/g,''); // if the string is empty, return all zeros if (s.match (/^\s*$/)) { var a = new Array; for (var i=0; i length*2) return 0; // error: string to long if (s.length & 1) return 0; // error: an even number of characters needed // pad string with zeros while (s.length < length*2) { s = s + '0'; } var a = new Array; for (var i=0; i < length; i++) { a[i] = parseInt(s.substr(i*2,2),16); } return a; } else return 0; } /* * byteArrayToHexString() * Convert a byte array 'a' into a hex string. Only array indexes * between start_index and end_index-1 are examined. * If the 'separator' argument is given it will be used to separate the bytes. */ function byteArrayToHexString (a,start_index,end_index,separator) { var s = ""; if (typeof(separator) != "string") separator = ""; for (var i=start_index; i length) { alert ('The string "' + value + '" is too long\n(maximum length is ' + length + ' characters).'); byte_array_has_error = 1; return; } appendArray (byte_array,stringToByteArray (value,length)); } /* * sC() * Single byte character. */ function sC() { var value = String.fromCharCode (byte_array[i]); i++; return value; } /* * gS() * Single byte character. */ function gC (value) { if (byte_array_has_error) return; byte_array[byte_array.length] = value.charCodeAt (0); } /* * sU() * Unsigned integer of the given number of bytes (1 to 4). */ function sU (length) { var value = byteArrayToInt (byte_array,i,i+length); i += length; if (length == 4 && value < 0) { // byteArrayToInt() returns a signed value if length==4, so compensate value = value + 4294967296; } return value; } /* * gIU_Helper() * Helper function for gI() and gU() */ function gIU_Helper (value,length,signed) { if (byte_array_has_error) return; var v; if (value==true || value=="true") { v = 1; } else if (value==false || value=="false") { v = 0; } else { v = convertNumber (value); if (v == null) { alert ('The number "' + value + '" is not valid.'); byte_array_has_error = 1; return; } } if (signed==0 && v < 0) { alert ('The number "' + value + '" must be positive.'); byte_array_has_error = 1; return; } // range checking var min,max; if (length <= 3) { if (signed==0) { max = (1<<(length*8))-1; min = 0; } else { max = (1<<(length*8-1))-1; min = -max-1; } } else { if (signed==0) { max = 4294967295; min = 0; } else { max = 2147483647; min = -max-1; } } if (v < min || v > max) { alert ('The number "' + value + '" is not in the correct range.'); byte_array_has_error = 1; return; } appendArray (byte_array,intToByteArray (v,length)); } /* * gU() * Unsigned integer of the given number of bytes (1 to 4). */ function gU (value,length) { gIU_Helper (value,length,0); } /* * sI() * Signed integer of the given number of bytes (1 to 4). */ function sI (length) { var value = byteArrayToInt (byte_array,i,i+length); i += length; var topbit = 1 << (length*8-1); if (length < 4 && (value & topbit) != 0) { // do a sign conversion within the lower 'length' bytes. if (value == topbit) { value = -topbit; } else { value = -( ((~value)+1) & (topbit-1) ); } } return value; } /* * gI() * Signed integer of the given number of bytes (1 to 4). */ function gI (value,length) { gIU_Helper (value,length,1); } /* * sH() * Hex string of the given length (in bytes). */ function sH (length,separator) { if (length==6 && separator==null) { separator = ':'; // hack to get MAC addresses to display with colons } var value = byteArrayToHexString (byte_array,i,i+length,separator); i += length; return value; } /* * gH() * Hex string of the given length (in bytes). */ function gH (value,length) { if (byte_array_has_error) return; var h = hexStringToByteArray (value,length); if (h==0) { alert ('The hex string "' + value + '" is not valid.') byte_array_has_error = 1; return; } appendArray (byte_array,h); } /* * sX() * IP address. */ function sX() { var value = byteArrayToIPAddress (byte_array,i); i += 4; return value; } /* * gX() * IP address. */ function gX (value) { if (byte_array_has_error) return; var ip = IPAddressToByteArray (value); if (ip==0) { alert ('The IP address "' + value + '" is not valid.') byte_array_has_error = 1; return; } appendArray (byte_array,ip); } //*************************************************************************** // Helper functions for various user interface elements function doExpandCollapse (e) { var obj = getEventObject (e); var s = getAttribute (obj,'_ALT_VALUE'); obj.setAttribute ('_ALT_VALUE',obj.value); obj.value = s; var v = getAttribute (obj,'_TOGGLE_VARIABLE'); eval (v + ' = !' + v); dataChanged(); } //*************************************************************************** // Array editor class /* * lookForUnusedEntry() * Look for the first unused entry in an array given an arrayEditor object. * Return -1 if all entries are used. */ function lookForUnusedEntry (ae) { var i; if (ae.is_stack == 1) { for (i=ae.max_elements-1; i>=0; i--) { if (ae.thearray[i].used==0) return i; } } else { for (i=0; i= 0 ? this.sel : lookForUnusedEntry (this); if (i < 0) { alert ('There is no room for any more entries.'); return 0; } /* * Check for blank or duplicate primary keys */ if (typeof(this.primary_key)=="string") { if (this.thearray[-1][this.primary_key] == "") { alert ("The '"+this.primary_key_name+"' field can not be blank"); return 0; } this.thearray[-1][this.primary_key] = trimString(this.thearray[-1][this.primary_key]); for (var j=0; j 0) { if (!confirm ("You have unsaved changes in the entry you are editing.\n"+ "Press 'Ok' to abandon these changes and perform the requested action.\n"+ "Otherwise press 'Cancel'.")) { return 0; } } return 1; } /* * arrayEditor:checkIfEditing() * Return 1 if there is changed-but-unsaved data in the array itself. * Return 2 if there are new edits not yet saved in the array. * Otherwise return 0. */ function arrayEditor_checkIfEditing (field_to_ignore) { if (this.sel >= 0) { if (compareObject (this.thearray[-1],this.thearray[this.sel],field_to_ignore) == 0) { return 1; } } else { /* * Prepare element [-2] to be a cleaned clone of a proper element. */ this.thearray[-2] = cloneObject (this.thearray[0]); if (this.clear_entry_function_valid) { this.clear_entry_function(-2); } /* * Compare the clean element against editing element (-1) to see if the user has * made changes. */ if (compareObject (this.thearray[-1],this.thearray[-2]) == 0) { return 2; } } return 0; } function arrayEditor_cancelEditingEntry() { if (this.clear_entry_function_valid) { this.clear_entry_function (-1); } this.sel = -1; this.is_under_edit = 0; dataChanged(); } function generateArray (ae, has_enabled_column, has_edit_delete_column, elements) { for (var i=0; i'; if (has_enabled_column) { html += ''; } for (var j=0; j'; } if (has_edit_delete_column) { html += 'Edit'; html += 'Delete'; } html += ''; document.write (html); } } /* * anyPageArrayEditorsChanged() * Return 1 if any of the non- information-only array editors on the page * contain changed data and the user does not want to abandon it. * Otherwise make sure all the information is saved and return 0 if it * was saved successfully or 1 if not. */ function anyPageArrayEditorsChanged() { if (typeof(page_array_editors) != 'undefined') { for (var i=0; i= 0) { fromform = element.options [element.selectedIndex].value; } } break; } if (data_to_form) { if (toform != '') eval (toform); } else { if (fromform != null && getAttribute (element,'_READONLY')==null) { eval (name + '=fromform'); } } } } } if (!data_to_form) { naturalizeDataObject(); } } //*************************************************************************** // Form submission function doSave() { if (anyPageArrayEditorsChanged()) { return 0; } formChanged (null); if (typeof(pageVerify) == "function") { if (!pageVerify()) return; } byte_array_has_error = 0; createBinaryArrayFromDataObject(); if (byte_array_has_error) return; /* * Post the new configuration to the server */ document.postform.data.value = convertToBase64 (byte_array); document.postform.submit(); } function doCancel() { if (confirm ("Do you want to abandon all changes you made to this page?")) { if (typeof(onCancelPage) == "function") { onCancelPage(); } uigenInitAtStart (false); if (typeof(onCancelComplete) == "function") { onCancelComplete(); } dataChanged(); } } function restoreError (message) { alert ("The restoration of settings failed ("+message+")\nPress OK to continue"); restore_error = 1; history.go(-1); return 0; } function showRestoreMessage (i) { eval ('restore_phase_done_' + i + ' = 1'); executeActiveTags(); } function doRestore (phase) { if (phase==1) { restore_error = 0; } else { if (restore_error) return; } if (phase==1) { /* * Convert the data string ('data' comes from the server) */ restore_error = 0; byte_array = convertFromBase64 (data); showRestoreMessage (1); } else if (phase==2) { /* * Create the local data object (unpackAll() comes from the server). * We set all data to the existing values so that (1) values not specified in the * saved file will not be changed, and (2) there will be no undefined values * when we repack. */ data = null; unpackAll(); showRestoreMessage (2); /* * Reset variables that will be loaded from the saved settings file. */ saved_data_object = data; data = null; unpackAll = null; } else if (phase==3) { /* * Convert the data string ('data' comes from the saved settings file) */ if (typeof(data) != "string") { return restoreError ("bad settings file"); } byte_array = convertFromBase64 (data); showRestoreMessage (3); } else if (phase==4) { /* * Unpack the saved settings into the data object (unpackAll() comes from the settings file) */ data = saved_data_object; if (typeof(unpackAll) != "function") { return restoreError ("Bad settings file"); } unpackAll(); showRestoreMessage (4); } else if (phase==5) { /* * Repack the data object into byte_array (packAll() comes from the server) */ byte_array_has_error = 0; packAll(); if (byte_array_has_error) { return restoreError ("Restored data not acceptable"); } showRestoreMessage (5); } else if (phase==6) { /* * Post the new configuration to the server */ document.postform.data.value = convertToBase64 (byte_array); showRestoreMessage (6); document.postform.submit(); } } //*************************************************************************** // Send data to the server and get returned status information without needing // a page load. This is done be loading an IFRAME block. // Data may be encoded in the URL thusly: "foo.cgi?data=...." var iframeCallback; var iframeDoneMessage; var iframeNumber = 0; var iframeCheckCount = 0; function iframeStandardCallback (iframe) { alert (iframeDoneMessage); } function checkIframeLoad() { iframeCheckCount++; if (iframeCheckCount > 60) { alert ("The action can not complete because the network connection seems to be down"); location.reload (true); return; } if (typeof(iframeCallback) == "function") { try { var doc = eval('window.frames.myframe_'+iframeNumber+'.window.document'); var data = getDataFromXML (doc); if (typeof(data)=="string" && data.length > 0) { iframeCallback (doc); return; } } catch (err) { } window.setTimeout ('checkIframeLoad();',100); } } /* * sendDataToServer() * Load an arbitrary URL from the server. Call the callback * function when the returned data is available (or if it is a string, display that * message when done). */ function sendDataToServer (url,callback_or_string) { if (typeof(callback_or_string) != "function") { if (typeof(callback_or_string) == "string") iframeDoneMessage = callback_or_string; else iframeDoneMessage = "Done"; callback_or_string = iframeStandardCallback; } iframeCallback = callback_or_string; var ifr = document.createElement ('DIV'); ifr.style.visibility = 'hidden'; ifr.style.position = 'absolute'; ifr.style.top = '0px'; ifr.style.left = '0px'; iframeNumber++; ifr.innerHTML = '